home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / Libraries / VideoToolbox 97.08.16 / VideoToolboxSources / VBLInstall.c < prev    next >
Encoding:
Text File  |  1997-08-10  |  21.3 KB  |  494 lines  |  [TEXT/CWIE]

  1. /*
  2. VBLInstall.c
  3.  
  4. ********
  5. NEW FEATURE (9/5/96): You can now find out the time elapsed since the beginning of 
  6. the last blanking. (Based on a suggestion of Stefan Treue.) 
  7. vblData->microTicks is set to Microseconds at that time. 
  8. You compute time elapsed by comparing that with the current time 
  9. returned by Microseconds. All the Time Manager calls in VBLInstall.c have
  10. been replaced by Microseconds calls, which have less overhead (and equal accuracy). 
  11. To read about how to use the Microseconds trap, i suggest Minow's article in Develop,
  12. "Timing on the Macintosh"
  13. <http://devworld.apple.com/dev/techsupport/develop/issue26/minow.html>
  14.  
  15. 3/20/97:
  16. If you're running on PowerPC then the time, as returned by Seconds.c,
  17. is also stored in vblData->seconds, as a double. However, this feature
  18. is disabled when running on a 68K machine because doing floating point
  19. in an interrupt service routine is complicated when using the 68881 fpu,
  20. as explained in the Apple tech note:
  21. <http://devworld.apple.com/dev/technotes/hw/hw_22.html>
  22. And there's hardly any point since Seconds.c uses Microseconds() when
  23. running on a 68k machine, so the microTicks field has the same information,
  24. though admittedly in a slightly less convenient form. (You may wish to
  25. copy the code from Seconds.c to convert microTicks to seconds.)
  26. At present we disable the call to Seconds() if GENERATING68K. Strictly
  27. speaking, it would be enough to disable only if GENERATING68881, but it
  28. seems like a good idea to avoid spending a lot of time in the interrupt
  29. service routine (they block other interrupts), and floating operations are
  30. slow without an fpu.
  31. ***************
  32.  
  33. VBLInstall.c implements the Apple-recommended way of synchronizing your program to a video
  34. display. In the Macintosh, interrupt service routines run at a high processor
  35. priority, i.e. they block interrupts while they run, so Apple advises that they
  36. be very quick, to avoid missing interrupts. That is the approach taken here,
  37. using the interrupt service routines solely to bump a frame counter and set a
  38. newFrame flag to true. Typically your main program will iteratively test the
  39. newFrame flag and, when it's true, clear it and do some action that you want to
  40. do once per frame, in synch with the video frames.
  41.  
  42. OSErr VBLInstall(VBLTaskAndA5 *vblData,GDHandle device,int frames);
  43.  
  44. The first argument is a pointer to a user-supplied data structure. "device" is a
  45. handle to the video screen that you want to synchronize to, or NULL if you want
  46. to synchronize to the System VBL rate (usually 60.15 Hz). The last argument,
  47. "frames", specifies how many times you want the interrupt to occur before the
  48. routine disables itself. If frames<0 then the interrupt will recur indefinitely.
  49.  
  50. VBLInstall zeroes vblData->frame, and sets vblData->framesDesired and 
  51. vblData->framesLeft equal to "frames". While vblData->framesLeft!=0, at each 
  52. end-of-frame interrupt vblData->frame will be incremented and vblData->newFrame 
  53. will be set to 1. vblData->framesLeft will be decremented if
  54. it's >0, but left alone if it's negative. If vblData->framesLeft is decremented
  55. to zero then the interrupt service routine is done, and won't reenable the
  56. interrupt.
  57.  
  58. Here's a minimal program, extracted from NoiseVBL.c, that uses these routines to
  59. show a 100-frame movie, with a new image on each video frame:
  60.  
  61.     VBLTaskAndA5 noiseVBL;
  62.     GDHandle device=GetMainDevice();
  63.     int frames=100,error;
  64.  
  65.     noiseVBL.subroutine=NULL;                // request default subroutine
  66.     error=VBLInstall(&noiseVBL,device,frames);
  67.     if(error)PrintfExit("VBLInstall error %d\n",error);
  68.     noiseVBL.vbl.vblCount=1;    // enable the interrupt service routine
  69.     while(noiseVBL.framesLeft || noiseVBL.newFrame){
  70.         if(noiseVBL.newFrame){
  71.             noiseVBL.newFrame=0;
  72.             CopyBitsQuickly((BitMap *)&(movie[noiseVBL.frame])
  73.                 ,(BitMap *)*((CGrafPtr)window)->portPixMap
  74.                 ,&noiseImage[0].bounds,&window->portRect,srcCopy,NULL);
  75.         }
  76.     }
  77.     VBLRemove(&noiseVBL);
  78.  
  79. Note the while(noiseVBL.framesLeft || noiseVBL.newFrame). When we get to the
  80. last frame framesLeft will become zero and newFrame will become 1. We then
  81. zero newFrame, and show our last frame. framesLeft and newFrame will remain zero.
  82. (Thanks to Stefan Treue, treue@uni-tuebingen.de, for this correction.)
  83.  
  84. Apple warns that interrupt service routines and any data that they access must
  85. be locked into memory, not swapped out, e.g. due to operation of the Memory
  86. Manager or Virtual Memory. It is dangerous to allow your compiler to install
  87. debugging code into interrupt service routines. It is VERY important that you
  88. call VBLRemove() before quitting. Once installed, slot-based interrupt tasks
  89. keep going forever. (Turning off vblData->vbl.vblCount disables the routine, but
  90. doesn't remove it.) The tasks are not removed by the Finder or System when your
  91. application finishes, even though the interrupt service routine's code and data
  92. will probably be overwritten.
  93.  
  94. Actually, things aren't that bad, because I've added a call to _atexit()
  95. requesting that all the VBL tasks installed by VBLInstall() be removed whenever
  96. the program terminates, whether normally or abnormally. This prevents the
  97. otherwise very annoying crash that would accompany premature termination, e.g.
  98. by typing command-., while the VBL task is active.
  99.  
  100. Writing an interrupt service routine, to be called once per video frame, is
  101. slightly tricky. VBLInterruptServiceRoutine does all the dirty work, and then
  102. calls a user-supplied subroutine that does whatever you want. If all you want to
  103. do is advance a frame counter until you reach the desired number of frames then
  104. you may wish to use the default FrameSubroutine() instead of writing your own.
  105. Put the address of whatever routine you want to use into the "subroutine"
  106. element of the VBLTaskAndA5 structure, or, to request use of FrameSubroutine,
  107. supply the address NULL. However, if your compiler uses Universal Headers, then you 
  108. must supply a Universal Procedure Pointer instead of the subroutine address:
  109.  
  110. #if GENERATINGCFM
  111.     noiseVBL.subroutine=NewVBLProc(myRoutine);
  112. #else
  113.     noiseVBL.subroutine=myRoutine;
  114. #endif
  115.  
  116. FrameSubroutine() uses Microseconds(). If this program runs on an old System
  117. (pre 7) lacking the Microseconds trap, then
  118. SimpleVBLSubroutine() will be used instead. Microseconds() is used to discard
  119. spurious interrupts that occur within 3 ms of the previous. This is necessary
  120. because some 1991-1992 Apple video cards generate several interrupts during the
  121. vertical blanking interval, even though Apple's documentation clearly indicates
  122. that they should only generate one. The fix (holding off for 3 ms) was suggested
  123. by Raynald Comtois.
  124.  
  125. The 1992 Apple "Inside Macintosh: Processes" book explains how to code a task
  126. that is to be performed each time your video device produces a vertical blanking
  127. level (i.e. between video frames). It's quite tricky. Therefore I wrote a
  128. generic one, that in turn, calls your custom one, after all the tricky bits have
  129. been taken care of.
  130.  
  131. There are some subtleties to the VBL interrupt service routine. The main one is
  132. that all the Macintosh compilers reference global variables relative to register
  133. A5, but register A5 may have the wrong value (corresponding to a different
  134. application) at interrupt time. So we save the right value of A5 inside our
  135. structure and restore A5 before calling our Task().
  136.  
  137. A further subtlety is that the new generation of optimizing compilers might
  138. notice that A5 is being modified, so it would be dangerous to access globals at
  139. all in InterruptServiceRoutine(), even though it's safe to do so in Task(). In
  140. fact Task() doesn't use any globals, but it could.
  141.  
  142. The fact that the address of our structure is in register A0 when the interrupt
  143. service routine is called results from the fact that the low level hardware VBL
  144. interrupt is intercepted by the operating system, which then calls our routine.
  145.  
  146. The VBLTaskAndA5 struct extends Apple's VBLTask struct by adding some useful
  147. information at the end. You may choose to copy this, or define your own, adding
  148. more stuff at the end. However, you may not want to bother, considering that
  149. your interrupt service routine can freely access global variables.
  150. Alternatively, I added a generic pointer at the end, which you may use to pass
  151. the address of any stuff that you want to access within your subroutine.
  152.  
  153. Macintosh Technical Note 180 ("Multifinder Miscellanea"), p. 5 says:
  154. "GetVBLRec() returns the address of the VBLRec associated with our VBL task.
  155. This works because on entry into the VBL task, A0 points to the theVBLTask field
  156. in the VBLRec record, which is the first field in the record and that is the
  157. address we return. Note that this method works whether the VBLRec is allocated
  158. globally, in the heap...or...on the stack. ... This trick allows us to get to
  159. the saved A5, but it could also be used to get to anything we wanted to store in
  160. the record." (quoted by Jamie McCarthy in comp.sys.mac.prog 10/15/92.)
  161.  
  162. HISTORY:
  163. 8/22/92 dgp wrote it, based on code extracted from my NoiseVBL.c
  164. 8/26/92    dgp    added VBLRemoveAll(), which is automatically placed in the _atexit()
  165.             queue, so it all your VBL tasks will be removed from the queue when
  166.             your program exits, whether normal or abnormally.
  167. 9/9/92    dgp    fixed erroneous attempt to use slot zero when NULL device was passed.
  168. 9/10/92    dgp    added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
  169.             Memory book this isn't strictly necessary, since VBL tasks will 
  170.             be called only when it's safe.
  171. 9/17/92    dgp    Transferred FrameSubroutine() here from GDFrameRate.c. Now use 
  172.             FrameSubroutine() instead of SimpleVBLSubroutine() as the default,
  173.             provided we can use Timer.c, otherwise fall back to using 
  174.             SimpleVBLSubroutine.
  175. 10/9/92    dgp    Automatically set up and dispose of the timer used by FrameSubroutine.
  176.             Added a field to the VBLTaskAndA5 structure to hold the timer pointer,
  177.             instead of using up the generic ptr.
  178.             WARNING: old programs (written during 9/92) that explicitly request use of 
  179.             FrameSubroutine must be changed, because at that time it was the user's 
  180.             responsibility to set up and dispose of the timer, whereas it's now done 
  181.             for you. To remind you to make this change "FrameSubroutine" is no longer 
  182.             in VideoToolbox.h, it's treated as a private routine here. To obtain the 
  183.             services of FrameSubroutine, just specify a NULL in the VBLTaskAndA5 
  184.             subroutine field.
  185. 11/17/92 dgp In VBLRemove(), first disable the interrupt, then clean up.
  186.             Fixed error that could cause bus error in VBLRemoveAll.
  187.             VBLInstall() now installs VBLRemoveAll() only once in the _atexit()
  188.             queue, no matter how many times you call it.
  189. 11/24/92 dgp Minor updating of comments.
  190. 7/9/93    dgp    Test MATLAB in if() instead of #if. 
  191. 2/28/94    dgp    In response to query by Mike Tarr (tarr-michael@CS.YALE.EDU), changed 
  192.             frames argument from int to long, and now allow frames==-1 to request 
  193.             that the interrupt service routine continue working indefinitely.
  194. 3/5/94    dgp    In response to a bug report by Mike Tarr, finished the 2/28/94 change,
  195.             which I'd foolishly only done to SimpleVBLSubroutine and not to 
  196.             FrameSubroutine.
  197. 5/28/94    dgp    Made compatible with Apple's Universal Headers. I also attempted to
  198.             make the code PowerPC compatible, but that remains to be tested.
  199. 10/1/94    dgp    Added new "frame" field to VBLTaskAndA5 struct, which counts up from zero.
  200. 10/24/94 dgp Made declaration of GetA0() explicitly indicate that answer is returned in D0, for
  201.             better compatibility with Metrowerks CodeWarrior C.
  202. 10/27/94 dgp It is very annoying for the machine to crash anytime you try to quit your application
  203.             by escaping via MacsBugs escape to shell. The problem is that CodeWarrior 4.5 doesn't attach
  204.             the atexit() tasks to _EscapeToShell. So I do it here. 
  205. 5/3/95 dgp fixed test for presence of vm, which before was always returning false.
  206. 7/1/95 dgp just call AtExitToShell(), without any conditionals. Special cases, e.g. MATLAB,
  207.             are handled in AtExitToShell.c.
  208. 9/5/96    st Stefan Treue corrected an error in the frame counting in the example above.
  209.             Added Stefan's enhanced FrameSubroutineElapsed at end of file.
  210. 3/19/97    dgp    Eliminated the obsolete disabled code that used Timer.c.
  211.             Added "seconds" field to the VBLTaskAndA5 struct to provide the same information
  212.             (time of last VBL) as the existing microTicks field, but in a more convenient form.
  213. 3/20/97    dgp    David Brainard reported that this was crashing on 68K. Turns out that interrupt
  214.             service routines aren't allowed to use the 68881 fpu unless they save and restore
  215.             the fpu registers. And Apple advises against using it at all since some of its
  216.             operations are very lengthy and you have to wait for the current operation to
  217.             end. So we now call Seconds() only if GENERATINGPOWERPC.
  218. 3/26/97    dgp    Call Seconds() only if !GENERATING68881.
  219. 3/26/97    dgp    HoldMemory on Seconds() if vmPresent and USE_SECONDS.
  220. 3/26/97    dgp    Call Seconds() only if !GENERATING68K, since i'm not sure the call will work if
  221.             we don't set up the A4 (not A5) register for access to globals.
  222. 4/10/97    dgp    Eliminate stuff that was conditional on !UNIVERSAL_HEADERS.
  223. 4/14/97    dgp    Add A4 stuff to support accessing globals when compiled as 68K code resource.
  224. 4/14/97    dgp    Add call to HighPriorityTimeout.
  225. 4/21/97    dgp    Standardized the definition of SetA4().
  226. 4/21/97    dgp    Standardized the definition of GetA0().
  227. 4/21/97    dgp    Standardized the definition of GetA4() & GetA5().
  228. 5/30/97    dgp    Removed call to HighPriorityTimeout.
  229. 7/31/97    dgp    Made VBLTaskAndA5.microTicks and VBLTaskAndA5.seconds volatile, since they're modified
  230.             at interrupt time. This change is in VideoToolbox.h.
  231. 8/10/97    dgp    Added (UnsignedWide *) cast in call to Microseconds to make THINK C happy.
  232. */
  233. #include "VideoToolbox.h"
  234. #ifndef __TRAPS__
  235.     #include <Traps.h>
  236. #endif
  237. //#include <Retrace.h>
  238. void VBLRemoveAll(void);
  239. #if GENERATINGPOWERPC
  240.     void VBLInterruptServiceRoutine(register VBLTaskAndA5 *vblData);
  241. #else
  242.     void VBLInterruptServiceRoutine(void);
  243. #endif
  244. static void FrameSubroutine(VBLTaskAndA5 *vblData);
  245.  
  246. // This macro allows you to disable the use of Seconds() if that routine is too
  247. // slow on your computer to be called from an interrupt service routine.
  248. #if GENERATING68K            // Defined in Apple's ConditionalMacros.h
  249.     #define USE_SECONDS 0    // This MUST be 0, or 68881 will crash.
  250. #else
  251.     #define USE_SECONDS 1    // Put time of VBL into "seconds" field of VBLTaskAndA5 struct.
  252. #endif
  253.  
  254. #if GENERATING68K
  255.     #pragma parameter __D0 GetA4()
  256.     long GetA4(void)= 0x200C;    // MOVE.L A4,D0
  257.     #pragma parameter __D0 GetA5()
  258.     long GetA5(void)= 0x200D;    // MOVE.L A5,D0
  259.     #if THINK_C
  260.         #pragma parameter __D0 SetA4(__D0)
  261.         long SetA4(long) = 0xC18C;    // EXG D0,A4
  262.     #else
  263.         long SetA4(long:__D0):__D0 = 0xC18C;    // EXG D0,A4
  264.     #endif
  265. #else
  266.     #define GetA4()        0L
  267.     #define GetA5()        0L
  268.     #define SetA4(x)    0L
  269. #endif
  270.  
  271. /*
  272. I don't know how to tell whether we're being compiled
  273. as an application or code resource.
  274. In a code resource, A4 is used as the global pointer.
  275. In an application, it's A5. So we have to know.
  276. At present we assume that we're in an application
  277. unless we're being compiled as a code resource for MATLAB.
  278. */
  279. #define CODE_RESOURCE MATLAB
  280.  
  281. /* This is just for reference. The original is in VideoToolbox.h
  282. struct VBLTaskAndA5 {
  283.     volatile VBLTask vbl;
  284.     long ourA5;
  285.     #if USESROUTINEDESCRIPTORS || GENERATINGCFM
  286.         UniversalProcPtr subroutine;
  287.     #else
  288.         void (*subroutine)(struct VBLTaskAndA5 *vblData);
  289.     #endif
  290.     GDHandle device;
  291.     long slot;
  292.     volatile long newFrame;        // Boolean
  293.     volatile long frame;        // count up from zero
  294.     volatile long framesLeft;    // count down to zero
  295.     long framesDesired;
  296.     volatile UnsignedWide microTicks;    // Microseconds() at time of most recent VBL
  297.     volatile double seconds;            // Seconds() at time of most recent VBL
  298.     void *ptr;                    // use this for whatever you want
  299. };
  300. typedef struct VBLTaskAndA5 VBLTaskAndA5;
  301. */
  302. #define TASK_SIZE 1000    // Generous guess for size of routine
  303. static long vmPresent=0;
  304. static VBLUPP vblProcPtr,frameSubroutineProcPtr,simpleVBLSubroutineProcPtr;
  305.  
  306. OSErr VBLInstall(VBLTaskAndA5 *vblData,GDHandle device,long frames)
  307. // trivial, but verbose
  308. {
  309.     static Boolean firstTime=1,slotRoutinesAvailable,microsecondsAvailable;
  310.     static long timeManagerVersion=0;
  311.     
  312.     if(firstTime){
  313.         firstTime=0;
  314.         slotRoutinesAvailable=TrapAvailable(_SlotVInstall);
  315.         Gestalt(gestaltVMAttr,&vmPresent);
  316.         vmPresent &= 1L<<gestaltVMPresent;
  317.         AtExitToShell(VBLRemoveAll);
  318.         Gestalt(gestaltTimeMgrVersion,&timeManagerVersion);
  319.         vblProcPtr=NewVBLProc(VBLInterruptServiceRoutine);
  320.         frameSubroutineProcPtr=NewVBLProc(FrameSubroutine);
  321.         simpleVBLSubroutineProcPtr=NewVBLProc(SimpleVBLSubroutine);
  322.         // This is Apple's recommended way to determine whether the Microseconds
  323.         // trap is available. <http://devworld.apple.com/dev/qa/tb/tb16.html>
  324.         #if !defined(_Microseconds)
  325.             #define _Microseconds 0xa193 // from traps.h
  326.         #endif
  327.         microsecondsAvailable=TrapAvailable(_Microseconds);
  328.         #if USE_SECONDS
  329.             Seconds();    // call it once now, so we don't do initialization at VBL time.
  330.         #endif
  331.     }
  332.     vblData->device=device;
  333.     if(device!=NULL && (*device)->gdRefNum!=0 && slotRoutinesAvailable)
  334.         vblData->slot=GetDeviceSlot(device);
  335.     else vblData->slot=-1;
  336.     vblData->vbl.vblAddr=(void *)vblProcPtr;
  337.     vblData->vbl.qType=vType;
  338.     vblData->vbl.vblCount=0;    /* Initially disable the interrupt service routine */
  339.     vblData->vbl.vblPhase=0;
  340.     #if CODE_RESOURCE
  341.         vblData->ourA5=GetA4();
  342.     #else
  343.         vblData->ourA5=GetA5();
  344.     #endif
  345.     if(vblData->subroutine==NULL){
  346.         vblData->seconds=Nan;
  347.         vblData->microTicks.lo=vblData->microTicks.hi=0;
  348.         if(microsecondsAvailable){
  349.             Microseconds((UnsignedWide *)&vblData->microTicks);    // cast to satisfy THINK C
  350.             vblData->subroutine=(void *)frameSubroutineProcPtr;
  351.         }else vblData->subroutine=(void *)simpleVBLSubroutineProcPtr;
  352.     }
  353.     vblData->newFrame=0;
  354.     vblData->frame=0;
  355.     vblData->framesLeft=vblData->framesDesired=frames;
  356.     if(vmPresent){
  357.         HoldMemory(vblData,sizeof(*vblData));
  358.         HoldMemory(vblData->vbl.vblAddr,TASK_SIZE);
  359.         HoldMemory(vblData->subroutine,TASK_SIZE);
  360.         #if USE_SECONDS
  361.             HoldMemory(Seconds,TASK_SIZE);
  362.         #endif
  363.     }
  364.     if(vblData->slot>=0)return SlotVInstall((QElemPtr)vblData,vblData->slot);
  365.     else return VInstall((QElemPtr)vblData);
  366. }
  367.  
  368. OSErr VBLRemove(VBLTaskAndA5 *vblData)
  369. {
  370.     int error;
  371.  
  372.     // only remove it if we installed it
  373.     if(vblData->vbl.vblAddr != vblProcPtr)return 0;
  374.     if(vblData->slot>=0)error=SlotVRemove((QElemPtr)vblData,vblData->slot);
  375.     else error=VRemove((QElemPtr)vblData);
  376.     if(vmPresent){
  377.         UnholdMemory(vblData,sizeof(vblData));
  378.         UnholdMemory(vblData->vbl.vblAddr,TASK_SIZE);
  379.         UnholdMemory(vblData->subroutine,TASK_SIZE);
  380.         #if USE_SECONDS
  381.             UnholdMemory(Seconds,TASK_SIZE);
  382.         #endif
  383.     }
  384.     return error;
  385. }
  386.  
  387. void VBLRemoveAll(void)
  388. {
  389.     QHdrPtr qHeader;
  390.     QElemPtr q;
  391.     
  392.     qHeader=GetVBLQHdr();
  393.     q=qHeader->qHead;
  394.     while(q!=NULL){
  395.         VBLRemove((VBLTaskAndA5 *)q);    // only removes ours
  396.         q=q->qLink;
  397.     }
  398. }    
  399.  
  400. #if (THINK_C || THINK_CPLUS || SYMANTEC_C)
  401.     #pragma options(!profile)    // it would be dangerous to call the profiler from here
  402.     #pragma options(assign_registers,redundant_loads)
  403. #endif
  404. #if __MWERKS__ && __profile__
  405.     #pragma profile off            // on 68k it would be dangerous to call the profiler from here
  406. #endif
  407.  
  408. #if GENERATINGPOWERPC
  409.     void VBLInterruptServiceRoutine(register VBLTaskAndA5 *vblData)
  410.     {
  411.         CallVBLProc(vblData->subroutine,vblData);    // call user's task, pass data ptr
  412.     }
  413. #else
  414.     #if THINK_C
  415.         #pragma parameter __D0 GetA0
  416.         long GetA0(void)=0x2008;        /* MOVE.L A0,D0 */
  417.     #else
  418.         Ptr GetA0(void):__D0 =0x2008;    /* MOVE.L A0,D0 */
  419.     #endif
  420.  
  421.     void VBLInterruptServiceRoutine(void)
  422.     {
  423.         register long oldA;
  424.         register VBLTaskAndA5 *vblData;
  425.     
  426.         vblData = (VBLTaskAndA5 *)GetA0();
  427.         #if CODE_RESOURCE
  428.             oldA = SetA4(vblData->ourA5);
  429.         #else
  430.             oldA = SetA5(vblData->ourA5);
  431.         #endif
  432.         // call user's task, pass data ptr
  433.         #if GENERATINGCFM
  434.             // WARNING: uppVBLProcInfo is the wrong selector.
  435.             CallUniversalProc(vblData->subroutine,uppVBLProcInfo,vblData);
  436.         #else
  437.             (*vblData->subroutine)(vblData);
  438.         #endif
  439.         #if CODE_RESOURCE
  440.             SetA4(oldA);
  441.         #else
  442.             SetA5(oldA);
  443.         #endif
  444.     }
  445. #endif
  446.  
  447. // WARNING: setting/restoring A4 in VBLInterruptServiceRoutine, above, 
  448. // hasn't yet been tested. It's only relevant if we access globals
  449. // and compile as a 68K code resource.
  450. void SimpleVBLSubroutine(VBLTaskAndA5 *vblData)
  451. {
  452.     vblData->newFrame=1;
  453.     vblData->frame++;
  454.     if(vblData->framesLeft>0)vblData->framesLeft--;
  455.     if(vblData->framesLeft!=0)vblData->vbl.vblCount=1;
  456. }
  457.  
  458.  
  459. // WARNING: setting/restoring A4 in VBLInterruptServiceRoutine, above, 
  460. // hasn't yet been tested. It's only relevant if we access globals
  461. // and compile as a 68K code resource.
  462. static void FrameSubroutine(VBLTaskAndA5 *vblData)
  463. {
  464.     // The 1991-2 Apple video cards emit several vbl interrupts per frame,
  465.     // which violates Apple's documentation.
  466.     // So we ignore any interrupt that occurs within 3 ms
  467.     // of the most recent interrupt for that video device.
  468.     // Thus this frame counter should work correctly on all video cards.
  469.     // Suggested by Raynald Comtois.
  470.  
  471.     UnsignedWide microTicks;
  472.     unsigned long microS;
  473.  
  474.     Microseconds(µTicks);
  475.     #if USE_SECONDS
  476.         vblData->seconds=Seconds();
  477.     #endif
  478.     microS=microTicks.hi-vblData->microTicks.hi;
  479.     if(microS==0)
  480.         // the usual case
  481.         microS=microTicks.lo-vblData->microTicks.lo;
  482.     else
  483.         // can happen at most once every 36 minutes
  484.         microS=0xffffffffUL-vblData->microTicks.lo+microTicks.lo+1;
  485.     if(microS>3000){
  486.         // It's been more than 3 ms since last interrupt, so this one is valid.
  487.         vblData->microTicks=microTicks;
  488.         vblData->newFrame=1;                                // set new-frame flag
  489.         vblData->frame++;
  490.         if(vblData->framesLeft>0)vblData->framesLeft--;
  491.         if(vblData->framesLeft!=0)vblData->vbl.vblCount=1;    // re-enable interrupt
  492.     }else vblData->vbl.vblCount=1;                            // re-enable interrupt
  493. }
  494.